package com.noassertions.web;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.GenericValidator;
import org.apache.commons.validator.ValidatorException;
import org.iscreen.ValidationException;
import org.iscreen.ValidationServiceWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.xml.sax.SAXException;

import com.noassertions.data.DAOInterface;
import com.noassertions.domain.User;

@Controller
public class UserController {

	protected final Log log = LogFactory.getLog(getClass());
	private DAOInterface dao;
	private AuthenticationManager authenticationManager;
	private ValidationServiceWrapper userValidator;
    private MessageSource messageSource;

    @Autowired
    public UserController(DAOInterface dao, AuthenticationManager authenticationManager, ValidationServiceWrapper userValidator,  MessageSource messageSource) {
        this.dao = dao;
        this.authenticationManager = authenticationManager;
        this.userValidator = userValidator;
        this.messageSource = messageSource;
    }  
  
	
    /**
     * This method dumps the user info in the view so that it can be displayed (most likely as JSON)
     * This is the standard GET for the User endpoint in a RESTful SOA
     * This method doesn't expect a userid, but generates one by random
     */
	@RequestMapping(value = "/user/random", method = RequestMethod.GET)
	public String getRandomUser(@RequestParam(required=false) Integer userid, Model model)
	{
		User user = dao.getRandomUserByUserid();
		// Let's make sure we don't return the same random user as the one that was sent to us
		if (userid != null && userid > 0) {
			int count = 0;
			while (userid == user.getUserid() && count < 10) {
				user = dao.getRandomUserByUserid();
				log.debug("received userid param of " + userid + ", generating a new userid of: " + user.getUserid());
				count++;
			}			
		}
		
		log.debug("Returning random user with userid of: " + user.getUserid());
		
		// JSON View expects array of users
		ArrayList<User> users = new ArrayList<User>();
		users.add(user);
		model.addAttribute("users", users);
		return "user";
	}
	
	/**
	 * Display form for new user sign up
	 */
	@RequestMapping(value = "/user/new", method = RequestMethod.GET)
	public String newUser()
	{
		// TODO: check to see if user is already authenticated if so, make them sign out before creating a new user
		return "newuser";
	}

	/**
	 * This method dumps the user info in the view so that it can be displayed (most likely as JSON)
	 * This is the standard GET for the User endpoint in a RESTful SOA 
	 */
	@RequestMapping(value = "/user/{userid}", method = RequestMethod.GET)
	public String getUser(@PathVariable Integer userid, Model model)
	{
		ArrayList<User> users = dao.getUserByUserId(userid);
		model.addAttribute("users", users);
		return "user";
	}

	/**
     * This method receives a new user object from a spring form and will insert into DB if it is valid
     * This is the standard POST for the User endpoint in a RESTful SOA 
     * @throws NoSuchAlgorithmException 
	 * @throws IOException 
	 * @throws FileUploadException 
	 * @throws ParseException 
	 * @throws SAXException 
	 * @throws ValidatorException 
     */
	@RequestMapping(value = "/user", method = RequestMethod.POST)
	public String createNewUser(HttpServletRequest request, Model model) throws NoSuchAlgorithmException, FileUploadException, IOException, ParseException, SAXException
	{
		log.debug("received post request in createNewUser");
		
		// TODO: this form parsing should probably be factored out
		User user = new User();
		ServletFileUpload upload = new ServletFileUpload();
		FileItemIterator iter = upload.getItemIterator(request);
		while (iter.hasNext()) {
			FileItemStream item = iter.next();
			String name = item.getFieldName();
			InputStream inputStream = item.openStream();
			// Get fields from form and add them to the user bean
			if (item.isFormField()) {
				if (name.equals("birthdate")) {
					String dateString = Streams.asString(inputStream);
					if (GenericValidator.isDate(dateString, "yyyy-MM-dd", false)) {
						SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
						Date date = format.parse(dateString);
						user.setBirthdate(date);
						model.addAttribute("birthdate", user.getBirthdate());
					}
				}
				if (name.equals("emailaddress")) {user.setEmailaddress(Streams.asString(inputStream)); model.addAttribute("emailaddress", user.getEmailaddress());}
				if (name.equals("firstname")) {user.setFirstname(Streams.asString(inputStream)); model.addAttribute("firstname", user.getFirstname());}
				if (name.equals("lastname")) {user.setLastname(Streams.asString(inputStream)); model.addAttribute("lastname", user.getLastname());}
				if (name.equals("password")) {user.setPassword(Streams.asString(inputStream));}
				// a bit of spam checking (this field is hidden in our form and should be empty)
				if (name.equals("bread")) {
					if (!"".equals(Streams.asString(inputStream))) {
						log.debug("Possible spam, bread field was not empty");
						return "newuser";
					}
				}

			}
		}
		
		// User submitted info is complete, so let's validate
		boolean valid = true;
		
		// This business of catching validation issues is not good, but it's the iscreen approach
		try {
			// TODO: we don't check if emailaddress already exists in DB, we need to do this since this is the unique userid used by spring security
			// TODO: right now, we only see that the date field is not null (since we check format above), we should have a better date validator
			userValidator.validate(user);
		} catch (ValidationException e) {
			StringBuffer errors = new StringBuffer();
			ArrayList<String> failures = (ArrayList<String>) e.getFailureMessages();
			
			for (String failure : failures) {
				errors.append(failure); 
			}
			model.addAttribute("validationErrors", errors.toString());
			
			log.debug("user submission did not validate: " + e.getFailureMessages());
			valid = false;
		}
		
		if (valid) {
			log.debug("it appears as is if the submitted user is valid. committing to model");
			Date now = new Date();
			user.setAccountcreationdate(now);
			
			// Set complete profile flag to false
			// this is set to true when at least one fit is associated with user
			user.setCompleteprofile(false);

			// We don't want to store plain text passwords, so MD5 hash it
			String origPass = user.getPassword();
			MessageDigest messageDigest = MessageDigest.getInstance("MD5");  
			messageDigest.update(user.getPassword().getBytes(),0, user.getPassword().length());  
			String hashedPass = new BigInteger(1,messageDigest.digest()).toString(16);  
			if (hashedPass.length() < 32) {
				hashedPass = "0" + hashedPass; 
			}
			user.setPassword(hashedPass);

			dao.insertUser(user);

			// Programmatically authenticate user after they register
			Authentication authRequest = new UsernamePasswordAuthenticationToken(user.getEmailaddress(), origPass);
			Authentication result = authenticationManager.authenticate(authRequest);	
			SecurityContextHolder.getContext().setAuthentication(result);

			return "redirect:" + messageSource.getMessage("webAppUrl", null, Locale.getDefault()) + "user/new/confirm";
		}
		else {
			// returning with errors in model (from catch block above)
			return "newuser";
		}
	}
	
	/**
	 * Display form for new user confirmation
	 */
	@RequestMapping(value = "/user/new/confirm", method = RequestMethod.GET)
	public String newUser(Model model)
	{
		UserDetails principal = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
		model.addAttribute("emailAddress", principal.getUsername());
		return "newuserconfirm";
	}
}